#!/bin/bash
#func：Use Curl to check server status. writen by 923106@qq.com 20220322

CURL_CHECK_VER=20220324

DEFAULT_ENV_NAME=".env"
DEFAULT_USER_AGENT="curl_check/${CURL_CHECK_VER}"

funcGetCurPath(){
  [ ! -z ${_CUR_PATH} ] || _CUR_PATH=$(cd `dirname $0`; pwd)
}

funcGetFullName(){
  [ ! -z ${_SH_FULL_NAME} ] || _SH_FULL_NAME=`basename $0`
}

funcGetShNameAndFullName(){
  [ ! -z ${_SH_FULL_NAME} ] || funcGetFullName
  [ ! -z ${_SH_NAME} ] || _SH_NAME="${_SH_FULL_NAME%.*}"
}

_funcEcho(){
  if [ $# -gt 0 ]; then
    echo "${LOG_TAG}:$*"
  fi
}

#funcDebug <msg>
funcDebug(){
  LOG_TAG="DEBUG"
  if [ "01" == "0${DEBUG}" ]; then
    _funcEcho $@
  fi
}

#funcInfo <msg>
funcInfo(){
  LOG_TAG="INFO"
  _funcEcho $@
}

#funcWarn <msg>
funcWarn(){
  LOG_TAG="WARN"
  _funcEcho $@
}

#funcError <msg>
funcError(){
  LOG_TAG="ERROR"
  _funcEcho $@
  exit
}

#funcIfStartsWith <str> <pre_fix>
funcIfStartsWith() {
  if [ $# -eq 2 ]; then
    echo "${1}" | grep "^${2}" >/dev/null 2>&1
  else
    funcWarn "wrong parameters($*). funcIfStartsWith <str> <pre_fix>."
  fi
}

#funcMakeSureFolderExist <dir>
funcMakeSureFolderExist(){
    funcDebug "make sure folder exist..."
    if [ $# -eq 1 ]; then
      if [ ! -d "${1}" ];then
        mkdir -p "${1}"
        if [ $? != 0 ]; then
          funcError "fail to create ${1}"
        fi
      fi
    else
      funcError "wrong parameters($*). funcMakeSureFolderExist <dir>. "
    fi
}

#funcSendDingDingMsg <msg> <token> [keyword]
funcSendDingDingMsg(){
  funcDebug "sent dingtalk msg..."
  if [ $# -gt 1 ] && [ "0" != "0${1}" ] && [ "0" != "0${2}" ]; then
    if [ $# -gt 2 ] && [ "0" != "0${3}" ]; then
      _DINGTALK_KEYWORD=" ${3}"
    fi
    funcDebug "MSG=\"${1}${_DINGTALK_KEYWORD}\" TOKEN=${2} KEYWORD=${3}"
    _R=`curl -s "https://oapi.dingtalk.com/robot/send?access_token=${2}" \
    -H 'Content-Type: application/json' \
    -d "{\"msgtype\": \"text\",
        \"text\": {
              \"content\": \"${1}${_DINGTALK_KEYWORD}\"
        }
       }"`
    funcDebug "(dingtalk) $_R"
  else
    funcWarn "wrong parameters($*). funcSendDingDingMsg <msg> <token> [keyword]. "
  fi
}

#funcCreateEnvExample <CURL_CHECK_CONFIG_FILE> "<CONTENT>"
funcCreateEnvExample(){
  funcDebug "create env example..."
  if [ $# -eq 2 ]; then
    _CURL_ENV_PATH=`dirname $1`
    _CURL_ENV_FILE="$1"
  else
    funcError "wrong parameters($*). funcCreateEnvExample <CURL_CHECK_CONFIG_FILE> <CONTENT>."
  fi

  funcMakeSureFolderExist ${_CURL_ENV_PATH}

  funcDebug "create ${_CURL_ENV_FILE}.sample..."
  echo -e "${2}" > ${_CURL_ENV_FILE}.sample
  if [ $? != 0 ]; then
    funcError "fail to create ${_CURL_ENV_FILE}.sample"
  else
    funcInfo "Please Modify \"${_CURL_ENV_FILE}.sample\", Then save as \"${_CURL_ENV_FILE}\"!"
  fi
}

#funcLoadConfig <CURL_CHECK_CONFIG_FILE>
funcLoadConfig(){
  funcDebug "loading config..."
  if [ $# -eq 1 ]; then
    if [[ -f ${1} ]]; then
      . ${1}
      if [ $? != 0 ]; then
        funcError "fail to load config ${1}"
      fi
    else
      funcError "config ${1} not found!"
    fi
  else
    funcError "wrong parameters($*). funcLoadConfig <CURL_CHECK_CONFIG_FILE>."
  fi
}

funcAlert(){
  if [ $# -eq 1 ]; then
    if [ "0" != "0${CURL_CHECK_DINGTALK_TOKEN}" ]; then
      funcDebug "dingtalk send \"${1}:${CURL_CHECK_URL}\""
      funcSendDingDingMsg "${1}:${CURL_CHECK_URL}" "${CURL_CHECK_DINGTALK_TOKEN}" "${CURL_CHECK_DINGTALK_KEYWORD}"
    else
      funcDebug "CURL_CHECK_DINGTALK_TOKEN=\"\", skip funcSendDingDingMsg"
    fi
  else
    funcWarn "wrong parameters($*). funcAlert <msg>."
  fi
}

funcCheckTouchSignFile(){
  funcDebug "touch ${CURL_CHECK_SIGN_FILE}"
  touch ${CURL_CHECK_SIGN_FILE}
  if [ $? != 0 ]; then
    funcError "Fail to touch CURL_CHECK_SIGN_FILE(${CURL_CHECK_SIGN_FILE}) "
  fi
}

funcCheckRemoveSignFile(){
  funcDebug "rm -rf ${CURL_CHECK_SIGN_FILE}"
  rm -rf ${CURL_CHECK_SIGN_FILE}
  if [ $? != 0 ]; then
    funcError "Fail to remove CURL_CHECK_SIGN_FILE(${CURL_CHECK_SIGN_FILE}) "
  fi
}

funcMakeDefaultSignFileName(){
  funcDebug "makeing default CURL_CHECK_SIGN_FILE name..."
  MD5_SOURCE_STR=${CURL_CHECK_URL}_${CURL_CHECK_RESULT_REQUIRED_STR}
  MD5_STR=""
  if [ -f "/sbin/md5" ]; then
    MD5_STR=`echo ${MD5_SOURCE_STR}|/sbin/md5|awk {'printf $1'}`
  else
    if [ -f "/bin/md5sum" ]; then
      MD5_STR=`echo ${MD5_SOURCE_STR}|/bin/md5sum|awk {'printf $1'}`
    else
      if [ -f "/usr/bin/md5sum" ]; then
        MD5_STR=`echo ${MD5_SOURCE_STR}|/usr/bin/md5sum|awk {'printf $1'}`
      else
        funcWarn "no md5 command found."
      fi
    fi
  fi

  funcGetShNameAndFullName

  CURL_CHECK_SIGN_FILE="./._${_SH_NAME}/_URL_FAIL_${MD5_STR}"

  funcDebug "CURL_CHECK_SIGN_FILE=${CURL_CHECK_SIGN_FILE}"
}

funcCheckSignFile(){
  funcDebug "checking CURL_CHECK_SIGN_FILE..."
  if [ "0" == "0${CURL_CHECK_SIGN_FILE}" ]; then
    funcMakeDefaultSignFileName
  fi

  _CURL_CHECK_SIGN_FILE_WRITEABLE_FILE="${CURL_CHECK_SIGN_FILE}_WRITEABLE"

  if [ ! -f "${_CURL_CHECK_SIGN_FILE_WRITEABLE_FILE}" ]; then
    if [[ -d ${CURL_CHECK_SIGN_FILE} ]] || [[ -s ${CURL_CHECK_SIGN_FILE} ]]; then
      funcError "CURL_CHECK_SIGN_FILE(${CURL_CHECK_SIGN_FILE}) already exist or not a empty file, Please specifies another one."
    else

      _CURL_SIGN_PATH=`dirname ${CURL_CHECK_SIGN_FILE}`
      funcMakeSureFolderExist ${_CURL_SIGN_PATH}

      if [[ -f ${CURL_CHECK_SIGN_FILE} ]]; then
        funcCheckRemoveSignFile
        funcCheckTouchSignFile
      else
        funcCheckTouchSignFile
        funcCheckRemoveSignFile
      fi

      funcDebug "save CURL_CHECK_SIGN_FILE writeable checking result."
      touch "${_CURL_CHECK_SIGN_FILE_WRITEABLE_FILE}"
    fi
  else
      funcDebug "${_CURL_CHECK_SIGN_FILE_WRITEABLE_FILE} exist, skip writeable check."
  fi
}

funcMarkFailStatus(){
  funcDebug "mark fail status to file..."
  if [[ -f ${CURL_CHECK_SIGN_FILE} ]]; then
    MARKED_FAIL_STAUTS=1
  else
    MARKED_FAIL_STAUTS=0
  fi

  funcDebug "CURL_CHECK_SIGN_FILE=${CURL_CHECK_SIGN_FILE} MARKED_FAIL_STAUTS=${MARKED_FAIL_STAUTS}"


  if [ "0${1}" == "01" ]; then
    if [[ ${MARKED_FAIL_STAUTS} == 0 ]]; then
      funcDebug "mark status as fail"
      touch ${CURL_CHECK_SIGN_FILE}
      funcAlert "Error"
    else
      funcDebug "The state remains the same, skip alert of 'Error'"
    fi
  else
    if [[ ${MARKED_FAIL_STAUTS} == 1 ]]; then
      funcDebug "mark status as success"
      rm -rf ${CURL_CHECK_SIGN_FILE}
      funcAlert "Recovered"
    else
      funcDebug "The state remains the same, skip alert 'Recovered'"
    fi
  fi
}

funcFail(){
  funcInfo "fail!"
  funcMarkFailStatus 1
  if [ "0" != "0${CURL_CHECK_EXECUTE_FILE}" ]; then
    funcInfo "execute ${CURL_CHECK_EXECUTE_FILE}"
    bash ${CURL_CHECK_EXECUTE_FILE}
  fi
}

funcSuccess(){
  funcInfo "success!"
  funcMarkFailStatus 0
}

funcShowVersion(){
  echo "
Use Curl to check server status
ver:${CURL_CHECK_VER}
"
}

funcAddShToCrontab(){
  funcDebug "add sh to crontab ..."

  funcGetCurPath
  funcGetDefaultEnvFileName

  [[ -n ${CURL_CHECK_CONFIG} ]] || CURL_CHECK_CONFIG=${_CUR_PATH}/${_DEFAULT_ENV_FILE}

  if [ -f ${CURL_CHECK_CONFIG} ]; then

    if ! crontab -l | grep ${_SH_FULL_NAME}; then
       crontab -l | {
            cat
            echo "*/5 * * * * bash `echo ${_CUR_PATH}`/`echo ${_SH_FULL_NAME}` -l ${CURL_CHECK_CONFIG} >/dev/null 2>&1"
          } | crontab -
      funcInfo "Cron installed"
    else
      funcInfo "Cron exist,skip"
    fi
  else
    funcError "${CURL_CHECK_CONFIG} not found, Abort!"
  fi
}

funcGetDefaultEnvFileName(){
  [ -z ${_DEFAULT_ENV_FILE} ] && funcGetShNameAndFullName && _DEFAULT_ENV_FILE="._${_SH_NAME}/${DEFAULT_ENV_NAME}"
}

funcCreateEnvExampleOfCurlCheck(){
  funcGetFullName
  funcDebug "create env example for ${_SH_FULL_NAME}"
  if [ "0" != "0${CURL_CHECK_CONFIG}" ]; then
    ENV=$(cat << EOF
#URL To Check
CURL_CHECK_URL=https://example.org

#KeyWord
CURL_CHECK_RESULT_REQUIRED_STR='"code":0'

#Not Want KeyWord, Default 0
CURL_CHECK_RESULT_WANTED_NOT=0

#USER_AGENT(Option)
CURL_CHECK_USER_AGENT=

#Specifies Temp FileName With Path(Option)
CURL_CHECK_SIGN_FILE=

#SH To Run After Fail(Option)
CURL_CHECK_EXECUTE_FILE=

#DingTalk Token(Option)
CURL_CHECK_DINGTALK_TOKEN=

#DingTalk KeyWord(Option)
CURL_CHECK_DINGTALK_KEYWORD=

EOF
  )
    funcDebug "config=${CURL_CHECK_CONFIG}"
    funcCreateEnvExample "${CURL_CHECK_CONFIG}" "${ENV}"
  else
    funcError "CURL_CHECK_CONFIG empty!"
  fi
}

funcShowhelp(){
  funcGetCurPath
  funcGetShNameAndFullName
  funcShowVersion

  funcGetDefaultEnvFileName

  echo "Usage: ${_SH_FULL_NAME} [Commands] <URL> <RESULT_REQUIRED_STR> [SIGN_FILE]

Parameters:
  <URL>                   URL to check, required!
  <RESULT_REQUIRED_STR>   check string, required!
  [SIGN_FILE]             sign file name, default is ./._${_SH_NAME}/_URL_FAIL_XXX

Commands:
  -h, --help        Show this help message.
  -v, --version     Show version.
  -d, --debug       Enable debug mode.
  -n, --not         Wanted not found RESULT_REQUIRED_STR in cURL result.
  -u, --user_agent  <USER_AGENT> Specifies a user-agent.
  -i, --install     [CONFIG_FILE] Install ${_SH_FULL_NAME} (default CONFIG_FILE is './${_DEFAULT_ENV_FILE}') to cron and run per 5 minutes(edit it by crontab -e).
  -l, --load        [CONFIG_FILE] Load config(create sample file if not exist), default is ./${_DEFAULT_ENV_FILE}.
  -e, --exec        <EXECUTE_FILE>      Specifies a sh to run after fail(option).
  -t, --token       <DINGTALK_TOKEN>    Specifies dingtalk token(option).
  -k, --keyword     <DINGTALK_KEYWORD>  Specifies dingtalk keyword(option).

Example:
  ${_SH_FULL_NAME} -l                           (load parameters from ./${_DEFAULT_ENV_FILE} OR create ./${_DEFAULT_ENV_FILE}.sample)
  ${_SH_FULL_NAME} -l /etc/abc.env              (load parameters from /etc/abc.env OR create /etc/abc.env.sample)
  ${_SH_FULL_NAME} http://abc.com '\"code\":0'    ('code:0' wanted)
  ${_SH_FULL_NAME} -n http://abc.com 'ABC' /var/check_abc     ('ABC' not wanted, And specifies SIGN_FILE to /var/check_abc*)
"
}

funcGetParams(){
  funcDebug "get parames..."
  #default
  CURL_CHECK_RESULT_WANTED_NOT=0
  _STR_CURL_CHECK_USER_AGENT=""

  while [ ${#} -gt 0 ]; do
    case "${1}" in
    --help | -h)
      funcShowhelp
      exit
      ;;
    --version | -v)
      funcShowVersion
      exit
      ;;
    --install | -i)
      if [[ -n ${2} ]] && ! funcIfStartsWith "${2}" "-" ; then
        CURL_CHECK_CONFIG=${2}
        shift
      fi
      funcAddShToCrontab
      exit
      ;;
    --debug | -d)
      DEBUG=1
      ;;
    --not | -n)
      CURL_CHECK_RESULT_WANTED_NOT=1
      ;;
    --load | -l)
      if [[ -n ${2} ]] && ! funcIfStartsWith "${2}" "-" ; then
        CURL_CHECK_CONFIG=${2}
        shift
      else
        funcGetDefaultEnvFileName
        CURL_CHECK_CONFIG="./${_DEFAULT_ENV_FILE}"
      fi
      if [ ! -f ${CURL_CHECK_CONFIG} ];then
        funcCreateEnvExampleOfCurlCheck
        exit
      else
        funcLoadConfig "${CURL_CHECK_CONFIG}"
      fi
      ;;
    --user_agent | -u)
      if [[ -n ${2} ]] && ! funcIfStartsWith "${2}" "-" ; then
        CURL_CHECK_USER_AGENT=${2}
        shift
      else
        funcError "$1 require a USER_AGENT parameter "
        exit
      fi
      ;;
    --exec | -e)
      if [[ -n ${2} ]] && ! funcIfStartsWith "${2}" "-" ; then
        CURL_CHECK_EXECUTE_FILE=${2}
        shift
      else
        funcError "$1 require a CURL_CHECK_EXECUTE_FILE parameter "
      fi
      ;;
    --token | -t)
      if [[ -n ${2} ]] && ! funcIfStartsWith "${2}" "-" ; then
        CURL_CHECK_DINGTALK_TOKEN=${2}
        shift
      else
        funcError "$1 require a CURL_CHECK_DINGTALK_TOKEN parameter "
      fi
      ;;
    --keyword | -k)
      if [[ -n ${2} ]] && ! funcIfStartsWith "${2}" "-" ; then
        CURL_CHECK_DINGTALK_KEYWORD=${2}
        shift
      else
        funcError "$1 require a CURL_CHECK_DINGTALK_KEYWORD parameter "
      fi
      ;;
    *)
      if ! funcIfStartsWith "${1}" "-" ; then
        if [ -z "${_CURL_CHECK_URL_FROM_CMD}" ]; then
          CURL_CHECK_URL=${1}
          _CURL_CHECK_URL_FROM_CMD=1
        else
          if [ -z "${CURL_CHECK_RESULT_REQUIRED_STR_FROM_CMD}" ]; then
            CURL_CHECK_RESULT_REQUIRED_STR=${1}
            CURL_CHECK_RESULT_REQUIRED_STR_FROM_CMD=1
          else
            if [ -z "${CURL_CHECK_SIGN_FILE_FROM_CMD}" ]; then
              CURL_CHECK_SIGN_FILE=${1}
              CURL_CHECK_SIGN_FILE_FROM_CMD=1
            fi
          fi
        fi
      else
        echo "Unknown parameter : $1"
        exit 1
      fi
      ;;
    esac
    shift 1
  done
}

funcCheckParameters(){
  funcDebug "checking Parameters..."
  if [ "0" == "0${CURL_CHECK_URL}" ]; then
    funcError "Parameter <URL> required! "
  fi

  if [ "0" == "0${CURL_CHECK_RESULT_REQUIRED_STR}" ]; then
    funcError "Parameter <CURL_CHECK_RESULT_REQUIRED_STR> required! "
  fi

  [[ -n ${CURL_CHECK_USER_AGENT} ]] || CURL_CHECK_USER_AGENT=${DEFAULT_USER_AGENT}

  _STR_CURL_CHECK_USER_AGENT="--user-agent ${CURL_CHECK_USER_AGENT}"

  funcCheckSignFile

  funcDebug "DEBUG=${DEBUG}"
  funcDebug "CURL_CHECK_URL=${CURL_CHECK_URL}"
  funcDebug "CURL_CHECK_RESULT_REQUIRED_STR=${CURL_CHECK_RESULT_REQUIRED_STR}"
  funcDebug "CURL_CHECK_RESULT_WANTED_NOT=${CURL_CHECK_RESULT_WANTED_NOT}"
  funcDebug "CURL_CHECK_SIGN_FILE=${CURL_CHECK_SIGN_FILE}"
  funcDebug "CURL_CHECK_EXECUTE_FILE=${CURL_CHECK_EXECUTE_FILE}"
  funcDebug "CURL_CHECK_DINGTALK_TOKEN=${CURL_CHECK_DINGTALK_TOKEN}"
  funcDebug "CURL_CHECK_DINGTALK_KEYWORD=${CURL_CHECK_DINGTALK_KEYWORD}"
  funcDebug "CURL_CHECK_USER_AGENT=${CURL_CHECK_USER_AGENT}"
  funcDebug "_STR_CURL_CHECK_USER_AGENT=${_STR_CURL_CHECK_USER_AGENT}"
}


funcCheckByCurl(){
  _CURL_CKECK_RESUTL=`curl -s ${_STR_CURL_CHECK_USER_AGENT} ${CURL_CHECK_URL}`

  if [ 0 == $? ]; then
    echo ${_CURL_CKECK_RESUTL} | grep "${CURL_CHECK_RESULT_REQUIRED_STR}" >/dev/null 2>&1
    _RESULT=$?

    if [ ${_RESULT} == ${CURL_CHECK_RESULT_WANTED_NOT} ]; then
      funcSuccess
      return
    fi
  fi

  funcFail
}

# "begin..."

if [ $# -eq 0 ]; then
  funcGetParams -h
else
  funcGetParams $@
fi

funcCheckParameters

funcCheckByCurl

funcDebug "end."
